<?php
namespace Wikitten;

class MarkdownExtra extends \Michelf\MarkdownExtra
{
    /**
     * Get the URI path, without its last file
     *
     * @param string $address
     *
     * @return string
     */
    protected static function getUriPath($address)
    {
        $path = (false === empty(pathinfo($address)['extension']))? dirname($address): $address;
        $path = ('.' === $path || '..' === $path)? '': $path;
        return ('/' === $path[strlen($path)-1])? $path: $path . '/';
    }

    /**
     * Get the current URI path, without its last file
     *
     * @return string
     */
    protected static function getCurrentUriPath()
    {
        return self::getUriPath($_SERVER['REQUEST_URI']);
    }

    /**
     * Check if a URL is external
     *
     * @param string $address
     *
     * @return boolean
     */
    protected static function isExternal($address)
    {
        try {
            if (false === empty(preg_match('/^(?!www\.|(?:http|ftp)s?:\/\/|[A-Za-z]:\\|\/\/).*/', trim($address)))) {
                return false;
            }
            $domain = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://$_SERVER[HTTP_HOST]";
            $parsed = parse_url($address);
            if (false === empty($parsed['scheme']) || false === empty($parsed['host']) || false === empty($parsed['port'])) {
                $parsedDomain = ((false === empty($parsed['scheme']))? $parsed['scheme']: '') . '://';
                $parsedDomain .= (false === empty($parsed['host']))? $parsed['host']: '';
                $parsedDomain .= (false === empty($parsed['port']))? ':' . $parsed['port']: '';
                return !($domain === $parsedDomain);
            }
            return true;
        } catch (\Exception $ex) {
            return true;
        }
    }

    /**
     * Add the target property to a link tag
     *
     * @param string $element
     * @param string $url
     *
     * @return string
     */
    protected function addTargetToAnchors($element, $url)
    {
        if (false === $this->isExternal($url)) {
            return $element;
        }
        if (false !== strpos($element, 'target="')) {
            $element = preg_replace('/target="[^\"]+"/', 'target="' . EXTERNAL_LINK_TARGET . '"', $element);
        } else {
            $element = str_replace('a href=', 'a target="' . EXTERNAL_LINK_TARGET . '" href=', $element);
        }

        return $element;
    }

    /**
     * Add missing path to a given URI
     *
     * @param string $url The URL/URI to be fixed
     *
     * @return string The fixed $url
     */
    protected static function fixUri($url)
    {
        // Nothing to do
        if (true !== INTERNAL_WIKI_LINK || true === self::isExternal($url) || '/' === $url[0]) {
            return $url;
        }

        // Just append current uri to the $url
        if (false === strpos($url, '/')) {
            return self::getCurrentUriPath() . $url;
        }

        // Get the filename, if there is any
        $lastDocument = [];
        $isFolder = (true === empty(preg_match("/\/[^\/]+$/", $url, $lastDocument)));
        $fileName = ltrim(((0 < count($lastDocument))? $lastDocument[0]: ''), '/');

        // Remove the file part of the $url
        $givenUrl = trim($url, '/');
        $givenUrl = (true === empty($givenUrl))? []: explode('/', $givenUrl);
        if (false === $isFolder && 0 < count($givenUrl)) {
            array_pop($givenUrl);
        }

        // Remove the '.' and go up a level for '..'
        $parentCount = 0;
        for ($i = count($givenUrl)-1; $i >= 0; $i--) {
            if (true === empty($givenUrl[$i]) || '.' === trim($givenUrl[$i])) {
                unset($givenUrl[$i]);
                continue;
            } elseif ('..' === trim($givenUrl[$i])) {
                $parentCount++;
                unset($givenUrl[$i]);
                continue;
            }
        }

        $givenUrl = join('/', $givenUrl);
        $givenUrl = true === empty($givenUrl)? '/': $givenUrl;
        $givenUrl .= '/' !== $givenUrl[strlen($givenUrl)-1]? '/': '';
        $givenUrl .= trim((('/' !== $givenUrl[0])? '/' . $fileName: $fileName), '/');

        // If there are no levels to go up, return the fixed URI.
        if (0 >= $parentCount) {
            return self::getCurrentUriPath() . $givenUrl;
        }

        $currentUri = trim(self::getCurrentUriPath(), '/');
        $currentUri = (true === empty($currentUri))? []: explode('/', $currentUri);
        // Remove empty slices generated by the explode function
        for ($i = count($currentUri)-1; $i >= 0; $i--) {
            if (true === empty($currentUri[$i])) {
                unset($currentUri[$i]);
            }
        }

        for ($i = 0; $i < $parentCount; $i++) {
            array_pop($currentUri);
        }

        $currentUri = join('/', $currentUri);
        $currentUri = ('/' !== $currentUri[0])? '/' . $currentUri: $currentUri;

        return rtrim($currentUri, '/') . '/' . trim($givenUrl, '/');
    }

    /**
     * Fix bar URI in links generated by markdown conversion
     *
     * @param string $element The HTML element
     * @param string $url The URL/URI to be fixed
     *
     * @return string The fixed element
     */
    protected function fixInternalWikiLinks($element, $url)
    {
        // Nothing to do
        if (true !== INTERNAL_WIKI_LINK) {
            return $element;
        }

        $url = self::fixUri($url);
        $element = preg_replace('/href=".*"/', "href=\"${url}\"", $element);
        $element = preg_replace('/src=".*"/', "src=\"${url}\"", $element);

        return $element;
    }

    /**
     * Fix bar URI in links already existing in paragraphs before markdown conversion
     *
     * @param string $text The paragraph text
     *
     * @return string The fixed paragraph
     */
    protected function fixParagraphLinks($text)
    {
        // Nothing to do
        if (true !== INTERNAL_WIKI_LINK) {
            return $text;
        }
        if (false === empty(preg_match('/src="([^"]+)+"/', $text, $matches))) {
            $matches = [];
            $text = preg_replace_callback('/src="([^"]+)+"/i', function($matches) {
                $url = 'src="' . $this->fixUri($matches[1]) . '"';

                return $url;
            }, $text);
        }

        if (false === empty(preg_match('/href="([^"]+)+"/', $text, $matches))) {
            $matches = [];
            $text = preg_replace_callback('/href="([^"]+)+"/i', function($matches) {
                $url = 'href="' . $this->fixUri($matches[1]) . '"';

                return $url;
            }, $text);
        }

        return $text;
    }

    /**
     * Callback for reference anchors
     * @param  array $matches
     * @return string
     */
    protected function _doAnchors_reference_callback($matches) {
        $whole_match =  $matches[1];
        $link_text   =  $matches[2];
        $link_id     =& $matches[3];

        if ($link_id == "") {
            // for shortcut links like [this][] or [this].
            $link_id = $link_text;
        }

        // lower-case and turn embedded newlines into spaces
        $link_id = strtolower($link_id);
        $link_id = preg_replace('{[ ]?\n}', ' ', $link_id);

        if (isset($this->urls[$link_id])) {
            $url = $this->urls[$link_id];
            $url = $this->encodeURLAttribute($url);

            $result = "<a href=\"$url\"";
            if ( isset( $this->titles[$link_id] ) ) {
                $title = $this->titles[$link_id];
                $title = $this->encodeAttribute($title);
                $result .=  " title=\"$title\"";
            }
            if (isset($this->ref_attr[$link_id]))
                $result .= $this->ref_attr[$link_id];

            $link_text = $this->runSpanGamut($link_text);
            $result .= ">$link_text</a>";
            $result = $this->addTargetToAnchors($result, $url);
            $result = $this->fixInternalWikiLinks($result, $url);
            $result = $this->hashPart($result);
        } else {
            $result = $whole_match;
        }
        return $result;
    }

    /**
     * Callback for inline anchors
     * @param  array $matches
     * @return string
     */
    protected function _doAnchors_inline_callback($matches) {
        $whole_match    =  $matches[1];
        $link_text        =  $this->runSpanGamut($matches[2]);
        $url            =  $matches[3] == '' ? $matches[4] : $matches[3];
        $title            =& $matches[7];
        $attr  = $this->doExtraAttributes("a", $dummy =& $matches[8]);

        // if the URL was of the form <s p a c e s> it got caught by the HTML
        // tag parser and hashed. Need to reverse the process before using the URL.
        $unhashed = $this->unhash($url);
        if ($unhashed != $url)
            $url = preg_replace('/^<(.*)>$/', '\1', $unhashed);

        $url = $this->encodeURLAttribute($url);

        $result = "<a href=\"$url\"";
        if (isset($title)) {
            $title = $this->encodeAttribute($title);
            $result .=  " title=\"$title\"";
        }
        $result .= $attr;

        $link_text = $this->runSpanGamut($link_text);
        $result .= ">$link_text</a>";
        $result = $this->addTargetToAnchors($result, $url);
        $result = $this->fixInternalWikiLinks($result, $url);

        return $this->hashPart($result);
    }

    /**
     * Callback for referenced images
     * @param  array $matches
     * @return string
     */
    protected function _doImages_reference_callback($matches)
    {
        $whole_match = $matches[1];
        $alt_text    = $matches[2];
        $link_id     = strtolower($matches[3]);

        if ($link_id == "") {
            $link_id = strtolower($alt_text); // for shortcut links like ![this][].
        }

        $alt_text = $this->encodeAttribute($alt_text);
        if (isset($this->urls[$link_id])) {
            $url = $this->encodeURLAttribute($this->urls[$link_id]);
            $result = "<img src=\"$url\" alt=\"$alt_text\"";
            if (isset($this->titles[$link_id])) {
                $title = $this->titles[$link_id];
                $title = $this->encodeAttribute($title);
                $result .=  " title=\"$title\"";
            }
            if (isset($this->ref_attr[$link_id]))
                $result .= $this->ref_attr[$link_id];
            $result .= $this->empty_element_suffix;
            $result = $this->hashPart($result);
        }
        else {
            // If there's no such link ID, leave intact:
            $result = $whole_match;
        }

        return $result;
    }

    /**
     * Callback for inline images
     * @param  array $matches
     * @return string
     */
    protected function _doImages_inline_callback($matches)
    {
        $whole_match    = $matches[1];
        $alt_text        = $matches[2];
        $url            = $matches[3] == '' ? $matches[4] : $matches[3];
        $title            =& $matches[7];
        $attr  = $this->doExtraAttributes("img", $dummy =& $matches[8]);

        $alt_text = $this->encodeAttribute($alt_text);
        $url = $this->encodeURLAttribute($url);
        $result = "<img src=\"$url\" alt=\"$alt_text\"";
        if (isset($title)) {
            $title = $this->encodeAttribute($title);
            $result .=  " title=\"$title\""; // $title already quoted
        }
        $result .= $attr;
        $result .= $this->empty_element_suffix;
        $result = $this->fixInternalWikiLinks($result, $url);

        return $this->hashPart($result);
    }

    /**
     * Parse text into paragraphs
     * @param  string $text String to process in paragraphs
     * @param  boolean $wrap_in_p Whether paragraphs should be wrapped in <p> tags
     * @return string       HTML output
     */
    protected function formParagraphs($text, $wrap_in_p = true) {
        // Strip leading and trailing lines:
        $text = preg_replace('/\A\n+|\n+\z/', '', $text);
        $text = $this->fixParagraphLinks($text);

        $grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY);

        // Wrap <p> tags and unhashify HTML blocks
        foreach ($grafs as $key => $value) {
            $value = trim($this->runSpanGamut($value));

            // Check if this should be enclosed in a paragraph.
            // Clean tag hashes & block tag hashes are left alone.
            $is_p = $wrap_in_p && !preg_match('/^B\x1A[0-9]+B|^C\x1A[0-9]+C$/', $value);

            if ($is_p) {
                $value = "<p>$value</p>";
            }
            $grafs[$key] = $value;
        }

        // Join grafs in one text, then unhash HTML tags.
        $text = implode("\n\n", $grafs);

        // Finish by removing any tag hashes still present in $text.
        $text = $this->unhash($text);

        return $text;
    }
}
